package com.bestv.search.engine.task;

import com.bestv.search.common.cache.SystemCache;
import com.bestv.search.common.dataSource.DataSourceKey;
import com.bestv.search.common.dataSource.DataSourceKeyUtil;
import com.bestv.search.common.model.BizType;
import com.bestv.search.common.model.NetInfo;
import com.bestv.search.common.model.TopKeyword;
import com.bestv.search.common.service.BizTypeManager;
import com.bestv.search.common.service.NetInfoManager;
import com.bestv.search.common.service.PersonManager;
import com.bestv.search.common.service.TopKeywordManager;
import com.bestv.search.common.util.StringConst;
import com.bestv.search.engine.compass.spellcheck.ChineseUtil;
import com.bestv.search.engine.compass.spellcheck.SpellCheckerWrapper;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class IndexForSpellCheckerSyncer extends QuartzJobBean {
    private static boolean isRunning = false;
    private final int WORD_SET_LIMIT = 100000;
    private Logger logger = Logger.getLogger(IndexForSpellCheckerSyncer.class);
    private PersonManager personManager;
    private TopKeywordManager topKeywordManager;
    private BizTypeManager bizTypeManager;
    private NetInfoManager netInfoManager;
    private SystemCache systemCache;

    @Override
    protected void executeInternal(JobExecutionContext arg0) throws JobExecutionException {
        if (!isRunning) {
            isRunning = true;
            final int limit = 1000;
            try {
                logger.info("Start spell checker index updates @ " + new Date());
                String localDs = systemCache.getLocalDataSourceKey();
                DataSourceKey key = DataSourceKeyUtil.convert(localDs);
                Set<String> spellCheckerWordSet = new HashSet<String>();

                // Add all persons to spell check index
                int count = personManager.getCount(key);
                int start = 0;
                do {
                    // Gets data from DB
                    List<String> personNames = personManager.getAllName(key, start, limit);
                    addDataToSpellCheckerWordSet(spellCheckerWordSet, personNames);
                    addSpellCheckerWordSetToIndex(spellCheckerWordSet, true);
                    start += limit;
                } while (start < count);

                // Adds topkeywords to spell checker index.
                List<TopKeyword> keywords = topKeywordManager.getAllTopKeywords(key);
                addDataToSpellCheckerWordSet(spellCheckerWordSet, keywords);
                addSpellCheckerWordSetToIndex(spellCheckerWordSet, true);

                // Add all netInfo name to spell check index
                count = netInfoManager.getNameCount(key);
                start = 0;
                do {
                    // Gets data from DB
                    List<String> netInfoNames = netInfoManager.getAllName(key, start, limit);
                    addDataToSpellCheckerWordSet(spellCheckerWordSet, netInfoNames);
                    addSpellCheckerWordSetToIndex(spellCheckerWordSet, true);
                    start += limit;
                } while (start < count);

                // Adds types to spell checker index.
                List<BizType> types = bizTypeManager.getAllBizTypes(key);
                addDataToSpellCheckerWordSet(spellCheckerWordSet, types);
                addSpellCheckerWordSetToIndex(spellCheckerWordSet, false);

                // print unknow chinese char
                if (ChineseUtil.unKnownChineseSet.size() > 0) {
                    logger.error("Unknown chinese char set " + ChineseUtil.unKnownChineseSet);
                }
            } catch (IOException e) {
                logger.error("Creates spell checker index failed " + e);
                e.printStackTrace();
            } finally {
                isRunning = false;
                logger.info("End spell checker index updates @ " + new Date());
            }
        } else {
            logger.info("Last spell checker index updates is still running");
        }
    }

    private <T> void addDataToSpellCheckerWordSet(Set<String> spellCheckerWordSet, List<T> data) {
        if (spellCheckerWordSet == null) {
            spellCheckerWordSet = new HashSet<String>();
        }

        for (T obj : data) {
            if (obj instanceof String) {
                spellCheckerWordSet.add(obj.toString());
            } else if (obj instanceof TopKeyword) {
                spellCheckerWordSet.add(((TopKeyword) obj).getKeyword());
            } else if (obj instanceof BizType) {
                spellCheckerWordSet.add(((BizType) obj).getPtype());
            } else if (obj instanceof NetInfo) {
                spellCheckerWordSet.add(((NetInfo) obj).getName());
            } else {
                logger.error("Un-support type for adding to dicFile " + obj.getClass().getName());
                break;
            }
        }
    }

    private <T> void createDicFileByData(String dicFilePath, Set<String> data) {
        // Creates dicFile
        File dicFile = new File(dicFilePath);
        if (dicFile.exists()) {
            dicFile.delete();
        }
        try {
            dicFile.createNewFile();
        } catch (IOException e) {
            logger.error("Creates new dic file failed " + e);
            e.printStackTrace();
        }

        // Adds data to dic file
        PrintWriter pw = null;
        try {
            pw = new PrintWriter(new FileOutputStream(dicFile), true);
            for (String word : data) {
                pw.write(word + StringConst.LINE_SEPARATOR);
                pw.flush();
            }
        } catch (IOException e) {
            logger.error("Adds data to dic file failed " + e);
            e.printStackTrace();
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }

    private void addSpellCheckerWordSetToIndex(Set<String> spellCheckerWordSet, boolean checkStatus) throws IOException {
        if (checkStatus) {
            // Just add words to index when set is full, else don't do anything
            if (spellCheckerWordSet.size() > WORD_SET_LIMIT) {
                // Gets dic file
                String dicFilePath = SpellCheckerWrapper.getDicFile();
                createDicFileByData(dicFilePath, spellCheckerWordSet);
                // Creates index
                SpellCheckerWrapper.createSpellIndex(dicFilePath, systemCache.getSpellCheckerIndex());
                // clear spellCheckerWordSet
                spellCheckerWordSet.clear();
                spellCheckerWordSet = new HashSet<String>();

            }
        } else {
            // Gets dic file
            String dicFilePath = SpellCheckerWrapper.getDicFile();
            createDicFileByData(dicFilePath, spellCheckerWordSet);
            // Creates index
            SpellCheckerWrapper.createSpellIndex(dicFilePath, systemCache.getSpellCheckerIndex());
            // clear spellCheckerWordSet
            spellCheckerWordSet.clear();
            spellCheckerWordSet = new HashSet<String>();
        }
    }

    public PersonManager getPersonManager() {
        return personManager;
    }

    public void setPersonManager(PersonManager personManager) {
        this.personManager = personManager;
    }

    public TopKeywordManager getTopKeywordManager() {
        return topKeywordManager;
    }

    public void setTopKeywordManager(TopKeywordManager topKeywordManager) {
        this.topKeywordManager = topKeywordManager;
    }

    public SystemCache getSystemCache() {
        return systemCache;
    }

    public void setSystemCache(SystemCache systemCache) {
        this.systemCache = systemCache;
    }

    public BizTypeManager getBizTypeManager() {
        return bizTypeManager;
    }

    public void setBizTypeManager(BizTypeManager bizTypeManager) {
        this.bizTypeManager = bizTypeManager;
    }

    public NetInfoManager getNetInfoManager() {
        return netInfoManager;
    }

    public void setNetInfoManager(NetInfoManager netInfoManager) {
        this.netInfoManager = netInfoManager;
    }
}